package ceylon.tool.converter.java2ceylon; import ceylon.tool.converter.java2ceylon.Java8Parser.*; import ceylon.tool.converter.java2ceylon.ScopeTree.Node; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import java.io.IOException; import java.io.Writer; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaToCeylonConverter extends Java8BaseVisitor<Void> { private boolean transformGetters; private boolean useValues; private Writer writer; private Pattern GETTER_PATTERN = Pattern.compile("(get|is)([A-Z]\\w*)"); private ScopeTree scopeTree; private ClassTypeContext superClass; private static final List<String> RESERVED_KEYWORDS = Arrays.asList( "assembly", "abstracts", "alias", "assert", "assign", "break", "case", "catch", "class", "continue", "dynamic", "else", "exists", "extends", "finally", "for", "function", "given", "if", "import", "in", "interface", "is", "module", "nonempty", "object", "of", "out", "outer", "package", "return", "satisfies", "super", "switch", "then", "this", "throw", "try", "value", "void", "while" ); public JavaToCeylonConverter(Writer out, boolean transformGetters, boolean useValues, ScopeTree scopeTree) { writer = out; this.transformGetters = transformGetters; this.useValues = useValues; this.scopeTree = scopeTree; } private void write(String str) { try { writer.write(str); } catch (IOException e) { throw new RuntimeException(e); } } private boolean hasModifier(List<? extends ParserRuleContext> modifiers, String modifier) { for (ParserRuleContext m : modifiers) { if (m.getText().equals(modifier)) { return true; } } return false; } private void addImport(Map<String, List<String>> importsByPackage, String pack, String type) { List<String> imports; pack = escapePackageIdentifiers(pack); if (importsByPackage.containsKey(pack)) { imports = importsByPackage.get(pack); } else { imports = new ArrayList<>(); importsByPackage.put(pack, imports); } // import on demand wins over single imports if (type.equals("...") && !imports.isEmpty()) { imports.clear(); } if (imports.size() == 1 && imports.get(0).equals("...")) { return; // No need to add a single import if there's already a wildcard } if(!imports.contains(type)) imports.add(type); } private String escapePackageIdentifiers(String pack) { StringBuilder builder = new StringBuilder(); for (String id : pack.split("\\.")) { if (builder.length() > 0) { builder.append("."); } builder.append(escapeIdentifier(id, false)); } return builder.toString(); } private void addStaticImport(Map<String, List<String>> staticImports, String pack, String type) { List<String> imports; if(staticImports.containsKey(pack)) { imports = staticImports.get(pack); } else { imports = new ArrayList<>(); staticImports.put(pack, imports); } // import on demand wins over single imports if (type.equals("...") && !imports.isEmpty()) { imports.clear(); } if (imports.size() == 1 && imports.get(0).equals("...")) { return; // No need to add a single import if there's already a wildcard } if(!imports.contains(type)) imports.add(type); } @Override public Void visitCompilationUnit(CompilationUnitContext ctx) { Map<String, List<String>> importsByPackage = new LinkedHashMap<>(); Map<String, List<String>> staticImports = new LinkedHashMap<>(); for (ImportDeclarationContext decl : ctx.importDeclaration()) { if (decl.singleTypeImportDeclaration() != null) { TypeNameContext typeName = decl.singleTypeImportDeclaration().typeName(); addImport(importsByPackage, typeName.packageOrTypeName().getText(), typeName.Identifier().getText()); } if (decl.typeImportOnDemandDeclaration() != null) { String pkgName = decl.typeImportOnDemandDeclaration().packageOrTypeName().getText(); addImport(importsByPackage, pkgName, "..."); } if(decl.singleStaticImportDeclaration() != null) { TypeNameContext typeName = decl.singleStaticImportDeclaration().typeName(); addImport(importsByPackage, typeName.packageOrTypeName().getText(), typeName.Identifier().getText()); addStaticImport(staticImports, typeName.getText(), decl.singleStaticImportDeclaration().Identifier().getText()); } if(decl.staticImportOnDemandDeclaration() != null) { TypeNameContext typeName = decl.staticImportOnDemandDeclaration().typeName(); addImport(importsByPackage, typeName.packageOrTypeName().getText(), typeName.Identifier().getText()); addStaticImport(staticImports, typeName.getText(), "..."); } } for (Map.Entry<String, List<String>> entry : importsByPackage.entrySet()) { String key = entry.getKey(); write("import "); write(key); write(" {\n"); for (int i = 0; i < entry.getValue().size(); i++) { if (i > 0) { write(",\n"); } String value = entry.getValue().get(i); write(value); if(staticImports.containsKey(key + "." + value)) { write("{\n"); List<String> imports = staticImports.get(key + "." + value); for(int j = 0; j < imports.size(); j++) { if(j > 0) write(", \n"); write(imports.get(j)); } write("\n}\n"); } } write("\n}\n"); } return super.visitCompilationUnit(ctx); } @Override public Void visitNormalClassDeclaration(NormalClassDeclarationContext ctx) { if (hasModifier(ctx.classModifier(), "public")) { write("shared "); } if (hasModifier(ctx.classModifier(), "static")) { write("static "); } if (hasModifier(ctx.classModifier(), "abstract")) { write("abstract "); } write("class "); String identifier = ctx.Identifier().getText(); identifier = Character.toUpperCase(identifier.charAt(0)) + identifier.substring(1); write(identifier); // TODO uppercase first letter if (ctx.typeParameters() != null) { visitTypeParameters(ctx.typeParameters()); } boolean hasExplicitConstructor = false; for (ClassBodyDeclarationContext decl : ctx.classBody().classBodyDeclaration()) { if (decl.constructorDeclaration() != null) { hasExplicitConstructor = true; break; } } if (!hasExplicitConstructor) { write("()"); } if (ctx.superclass() != null) { visitSuperclass(ctx.superclass()); if (!hasExplicitConstructor) { write("()"); } } if (ctx.superinterfaces() != null) { visitSuperinterfaces(ctx.superinterfaces()); } visitClassBody(ctx.classBody()); return null; } @Override public Void visitTypeParameters(TypeParametersContext ctx) { write("<"); visitTypeParameterList(ctx.typeParameterList()); write(">"); return null; } @Override public Void visitTypeParameterList(TypeParameterListContext ctx) { boolean isFirst = true; for (TypeParameterContext param : ctx.typeParameter()) { if (!isFirst) { write(", "); } visitTypeParameter(param); isFirst = false; } return null; } @Override public Void visitTypeParameter(TypeParameterContext ctx) { write(ctx.Identifier().getText()); if (ctx.typeBound() != null) { visitTypeBound(ctx.typeBound()); } return null; } @Override public Void visitWildcard(WildcardContext ctx) { WildcardBoundsContext bounds = ctx.wildcardBounds(); if (bounds != null) { if (bounds.getChild(0).getText().equals("extends")) { write("out "); } else { write("in "); } visitReferenceType((ReferenceTypeContext) bounds.getChild(1)); } else { write("out Object"); } return null; } @Override public Void visitTypeBound(TypeBoundContext ctx) { return super.visitTypeBound(ctx); } @Override public Void visitSuperclass(SuperclassContext ctx) { write(" extends "); superClass = ctx.classType(); super.visitSuperclass(ctx); return null; } @Override public Void visitSuperinterfaces(SuperinterfacesContext ctx) { write(" satisfies "); return super.visitSuperinterfaces(ctx); } @Override public Void visitClassType(ClassTypeContext ctx) { if (ctx.getChild(0) instanceof ClassOrInterfaceTypeContext) { visitClassOrInterfaceType((ClassOrInterfaceTypeContext) ctx.getChild(0)); write("."); } // TODO? annotations* write(ctx.Identifier().getText()); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitInterfaceTypeList(InterfaceTypeListContext ctx) { boolean isFirst = true; for (InterfaceTypeContext type : ctx.interfaceType()) { if (!isFirst) { write(" & "); } visitInterfaceType(type); isFirst = false; } return null; } @Override public Void visitClassBody(ClassBodyContext ctx) { write(" {\n\n"); Void result = super.visitClassBody(ctx); write("}\n"); return result; } @Override public Void visitMethodDeclaration(MethodDeclarationContext ctx) { if (hasModifier(ctx.methodModifier(), "public")) { write("shared "); } if (hasModifier(ctx.methodModifier(), "static")) { write("static "); } if (hasModifier(ctx.methodModifier(), "@Override")) { if(!hasModifier(ctx.methodModifier(), "public")) write("shared actual "); else write("actual "); } if (hasModifier(ctx.methodModifier(), "abstract")) { write("formal "); } visitMethodHeader(ctx.methodHeader()); visitMethodBody(ctx.methodBody()); write("\n"); return null; } @Override public Void visitMethodHeader(MethodHeaderContext ctx) { if (ctx.result().getText().equals("void")) { write("void "); } else { visitUnannType(ctx.result().unannType()); write(" "); } visitMethodDeclarator(ctx.methodDeclarator()); return null; } @Override public Void visitMethodDeclarator(MethodDeclaratorContext ctx) { String methodName = ctx.Identifier().getText(); Matcher matcher = GETTER_PATTERN.matcher(methodName); if (transformGetters && matcher.matches() && ctx.formalParameterList() == null) { String property = matcher.group(2); // TODO we should use NamingBase.getJavaBeanName() instead if (property.length() > 1) { property = Character.toLowerCase(property.charAt(0)) + property.substring(1); } else { property = property.toLowerCase(); } write(escapeIdentifier(property, true)); } else if ("toString".equals(methodName) && ctx.formalParameterList() == null) { write("string"); } else if ("hashCode".equals(methodName) && ctx.formalParameterList() == null) { write("hash"); } else { write(escapeIdentifier(methodName, true)); write("("); if (ctx.formalParameterList() != null) { visitFormalParameterList(ctx.formalParameterList()); } write(")"); } return null; } @Override public Void visitMethodBody(MethodBodyContext ctx) { if (ctx.block() == null) { write(";\n"); } else { write(" "); return super.visitMethodBody(ctx); } return null; } @Override public Void visitFormalParameterList(FormalParameterListContext ctx) { if (ctx.formalParameters() != null) { for (FormalParameterContext param : ctx.formalParameters().formalParameter()) { visitFormalParameter(param); write(", "); } } if (ctx.lastFormalParameter()!=null && ctx.lastFormalParameter().formalParameter() != null) { visitFormalParameter(ctx.lastFormalParameter().formalParameter()); } return null; } @Override public Void visitFormalParameter(FormalParameterContext param) { Node n = scopeTree.getNode(param.variableDeclaratorId(), scopeTree.root); if (n.variable && !hasModifier(param.variableModifier(), "final")) { write("variable "); } visitUnannType(param.unannType()); write(" "); write(escapeIdentifier(param.variableDeclaratorId().getText(), true)); return null; } @Override public Void visitUnannPrimitiveType(UnannPrimitiveTypeContext ctx) { String type = ctx.getText(); switch (type) { case "int": case "long": case "short": write("Integer"); break; case "float": case "double": write("Float"); break; case "boolean": write("Boolean"); break; case "byte": write("Byte"); break; case "char": write("Character"); break; default: write(type); } return null; } @Override public Void visitUnannTypeVariable(UnannTypeVariableContext ctx) { write(ctx.Identifier().getText()); return null; } @Override public Void visitUnannClassType_lfno_unannClassOrInterfaceType(UnannClassType_lfno_unannClassOrInterfaceTypeContext ctx) { write(ctx.Identifier().getText()); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitUnannClassType_lf_unannClassOrInterfaceType(UnannClassType_lf_unannClassOrInterfaceTypeContext ctx) { write("."); write(ctx.Identifier().getText()); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitTypeArguments(TypeArgumentsContext ctx) { write("<"); visitTypeArgumentList(ctx.typeArgumentList()); write(">"); return null; } @Override public Void visitTypeArgumentList(TypeArgumentListContext ctx) { boolean isFirst = true; for (TypeArgumentContext param : ctx.typeArgument()) { if (!isFirst) { write(", "); } visitTypeArgument(param); isFirst = false; } return null; } @Override public Void visitTypeVariable(TypeVariableContext ctx) { write(ctx.Identifier().getText()); return null; } @Override public Void visitClassType_lfno_classOrInterfaceType(ClassType_lfno_classOrInterfaceTypeContext ctx) { write(ctx.Identifier().getText()); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitClassType_lf_classOrInterfaceType(ClassType_lf_classOrInterfaceTypeContext ctx) { write("."); write(ctx.Identifier().getText()); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitNormalInterfaceDeclaration(NormalInterfaceDeclarationContext ctx) { if (hasModifier(ctx.interfaceModifier(), "public")) { write("shared "); } if (hasModifier(ctx.interfaceModifier(), "static")) { //TODO: or if it is any nested interface!!! write("static "); } write("interface "); String identifier = ctx.Identifier().getText(); identifier = Character.toUpperCase(identifier.charAt(0)) + identifier.substring(1); write(identifier); // TODO uppercase first letter if (ctx.typeParameters() != null) { visitTypeParameters(ctx.typeParameters()); } if (ctx.extendsInterfaces() != null) { visitExtendsInterfaces(ctx.extendsInterfaces()); } visitInterfaceBody(ctx.interfaceBody()); return null; } @Override public Void visitInterfaceMethodDeclaration(InterfaceMethodDeclarationContext ctx) { write("shared "); if (hasModifier(ctx.interfaceMethodModifier(), "default")) { write("default "); } else { write("formal "); } visitMethodHeader(ctx.methodHeader()); visitMethodBody(ctx.methodBody()); return null; } @Override public Void visitInterfaceBody(InterfaceBodyContext ctx) { write(" {\n"); for (InterfaceMemberDeclarationContext decl : ctx.interfaceMemberDeclaration()) { visitInterfaceMemberDeclaration(decl); } write("}\n"); return null; } @Override public Void visitExtendsInterfaces(ExtendsInterfacesContext ctx) { write(" satisfies "); return super.visitExtendsInterfaces(ctx); } @Override public Void visitConstructorDeclaration(ConstructorDeclarationContext ctx) { write("shared "); visitConstructorDeclarator(ctx.constructorDeclarator()); ExplicitConstructorInvocationContext child = ctx.constructorBody().explicitConstructorInvocation(); if(child != null) { for(ParseTree c : child.children) { if(c.getText().equals("super")) { write(" extends " + superClass.getText()); write("("); if (child.argumentList() != null) { visitArgumentList(child.argumentList()); } write(")"); break; } } } visitConstructorBody(ctx.constructorBody()); return null; } @Override public Void visitExplicitConstructorInvocation( ExplicitConstructorInvocationContext ctx) { for(ParseTree c : ctx.children) { if(c.getText().equals("super")) { return null; } } return super.visitExplicitConstructorInvocation(ctx); } @Override public Void visitConstructorDeclarator(ConstructorDeclaratorContext ctx) { write("new "); // TODO? name constructor write("("); if (ctx.formalParameterList() != null) { visitFormalParameterList(ctx.formalParameterList()); } write(")"); return null; } @Override public Void visitConstructorBody(ConstructorBodyContext ctx) { write(" {\n"); super.visitConstructorBody(ctx); write("}\n\n"); return null; } @Override public Void visitMethodInvocation(MethodInvocationContext ctx) { String name = null; if (ctx.methodName() != null) { write(escapeIdentifier(ctx.methodName().getText(), true)); } else if (ctx.typeName() != null) { String text = ctx.typeName().getText(); if (text.equals("System.out") && ctx.Identifier().getText().equals("println")) { name = "System.out.println"; } else { write(text); write("."); } } else if (ctx.expressionName() != null) { name = ctx.expressionName().getText() + "." + ctx.Identifier().getText(); } else if (ctx.primary() != null) { visitPrimary(ctx.primary()); write("."); } else if (ctx.typeName() != null) { visitTypeName(ctx.typeName()); write(".super."); } else { write("super."); } if (name != null) { switch (name) { case "System.out.println": name = "print"; break; } write(escapeIdentifier(name, true)); } else { if (ctx.Identifier() != null) { Matcher matcher = GETTER_PATTERN.matcher(ctx.Identifier().getText()); if (transformGetters && matcher.matches() && ctx.argumentList() == null) { String property = matcher.group(2); if (property.length() > 1) { property = Character.toLowerCase(property.charAt(0)) + property.substring(1); } else { property = property.toLowerCase(); } write(escapeIdentifier(property, true)); return null; } else { write(escapeIdentifier(ctx.Identifier().getText(), true)); } } if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } } write("("); if (ctx.argumentList() != null) { visitArgumentList(ctx.argumentList()); } write(")"); return null; } @Override public Void visitMethodInvocation_lfno_primary(MethodInvocation_lfno_primaryContext ctx) { String prefix = ""; String methodName = ""; if (ctx.Identifier() != null) { methodName = ctx.Identifier().getText(); } if (ctx.methodName() != null) { methodName = ctx.methodName().getText(); } else if (ctx.typeName() != null) { prefix = escapeIdentifier(ctx.typeName().getText(), false) + "."; if (ctx.getChild(2).getText().equals("super")) { prefix += "super."; } } else if (ctx.expressionName() != null) { prefix = ctx.expressionName().getText(); } else { prefix = "super."; } if ((prefix + methodName).equals("System.out.println")) { prefix = ""; methodName = "print"; } write(prefix); Matcher matcher = GETTER_PATTERN.matcher(methodName); if (transformGetters && matcher.matches() && ctx.argumentList() == null) { String property = matcher.group(2); if (property.length() > 1) { property = Character.toLowerCase(property.charAt(0)) + property.substring(1); } else { property = property.toLowerCase(); } write(escapeIdentifier(property, true)); } else if ("toString".equals(methodName) && ctx.argumentList() == null) { write("string"); } else if ("hashCode".equals(methodName) && ctx.argumentList() == null) { write("hash"); } else { write(escapeIdentifier(methodName, true)); write("("); if (ctx.argumentList() != null) { visitArgumentList(ctx.argumentList()); } write(")"); } return null; } @Override public Void visitMethodInvocation_lf_primary(MethodInvocation_lf_primaryContext ctx) { String methodName = ""; write("."); if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } if (ctx.Identifier() != null) { methodName = ctx.Identifier().getText(); } Matcher matcher = GETTER_PATTERN.matcher(methodName); if (transformGetters && matcher.matches() && ctx.argumentList() == null) { String property = matcher.group(2); if (property.length() > 1) { property = Character.toLowerCase(property.charAt(0)) + property.substring(1); } else { property = property.toLowerCase(); } write(escapeIdentifier(property, true)); } else if ("toString".equals(methodName) && ctx.argumentList() == null) { write("string"); } else if ("hashCode".equals(methodName) && ctx.argumentList() == null) { write("hash"); } else { write(escapeIdentifier(ctx.Identifier().getText(), true)); write("("); if (ctx.argumentList() != null) { visitArgumentList(ctx.argumentList()); } write(")"); } return null; } @Override public Void visitLiteral(LiteralContext ctx) { if (ctx.FloatingPointLiteral() != null) { double d = Double.parseDouble(ctx.FloatingPointLiteral().getText()); write(String.valueOf(d)); } else { write(ctx.getText()); } return super.visitLiteral(ctx); } @Override public Void visitReturnStatement(ReturnStatementContext ctx) { write("return "); if (ctx.expression() != null) { visitExpression(ctx.expression()); } write(";\n"); return null; } @Override public Void visitThrowStatement(ThrowStatementContext ctx) { write("throw "); if (ctx.expression() != null) { visitExpression(ctx.expression()); } write(";\n"); return null; } @Override public Void visitExpressionStatement(ExpressionStatementContext ctx) { super.visitExpressionStatement(ctx); write(";\n"); return null; } @Override public Void visitAssignment(AssignmentContext ctx) { ArrayAccessContext array = ctx.leftHandSide().arrayAccess(); // Bypass array assignment to replace it with a.set(b, c) if (array != null) { if (array.expressionName() != null) { visitExpressionName(array.expressionName()); } else { visitPrimaryNoNewArray_lfno_arrayAccess(array.primaryNoNewArray_lfno_arrayAccess()); } write(".set("); visitExpression(array.expression().get(0)); write(", "); visitExpression(ctx.expression()); write(")"); } else { super.visitAssignment(ctx); } return null; } @Override public Void visitArrayAccess_lfno_primary(ArrayAccess_lfno_primaryContext ctx) { // Bypass array access to replace it with a.get(b) if (ctx.expressionName() != null) { visitExpressionName(ctx.expressionName()); } else { visitPrimaryNoNewArray_lfno_primary_lfno_arrayAccess_lfno_primary( ctx.primaryNoNewArray_lfno_primary_lfno_arrayAccess_lfno_primary()); } write(".get("); visitExpression(ctx.expression().get(0)); write(")"); return null; } @Override public Void visitAssignmentOperator(AssignmentOperatorContext ctx) { write(" "); write(ctx.getText()); write(" "); return null; } @Override public Void visitUnannArrayType(UnannArrayTypeContext ctx) { if (ctx.unannPrimitiveType() != null) { String ceylonType; String type = ctx.unannPrimitiveType().getText(); switch (type) { case "int": ceylonType = "IntArray"; break; case "short": ceylonType = "ShortArray"; break; case "boolean": ceylonType = "BooleanArray"; break; case "byte": ceylonType = "ByteArray"; break; case "long": ceylonType = "LongArray"; break; case "float": ceylonType = "FloatArray"; break; case "double": ceylonType = "DoubleArray"; break; case "char": ceylonType = "CharArray"; break; default: ceylonType = "ObjectArray<" + type + ">"; break; } write(ceylonType); } else if (ctx.unannTypeVariable() != null) { write("ObjectArray<" + ctx.unannTypeVariable().Identifier().getText() + ">"); } else { write("ObjectArray<"); visitUnannClassOrInterfaceType(ctx.unannClassOrInterfaceType()); write(">"); } return null; } @Override public Void visitArrayCreationExpression(ArrayCreationExpressionContext ctx) { if (ctx.primitiveType() != null) { String ceylonType; String type = ctx.primitiveType().getText(); switch (type) { case "int": ceylonType = "IntArray"; break; case "short": ceylonType = "ShortArray"; break; case "boolean": ceylonType = "BooleanArray"; break; case "byte": ceylonType = "ByteArray"; break; case "long": ceylonType = "LongArray"; break; case "float": ceylonType = "FloatArray"; break; case "double": ceylonType = "DoubleArray"; break; case "char": ceylonType = "CharArray"; break; default: ceylonType = "ObjectArray<" + type + ">"; break; } write(ceylonType); if (ctx.arrayInitializer() != null) { write(".with"); } } else { write("ObjectArray<"); visitClassOrInterfaceType(ctx.classOrInterfaceType()); write(">"); if (ctx.arrayInitializer() != null) { write(".with"); } } write("("); if(ctx.arrayInitializer() != null) { visitArrayInitializer(ctx.arrayInitializer()); } else if (ctx.dimExprs() != null) { visitDimExprs(ctx.dimExprs()); } else { visitDims(ctx.dims()); } write(")"); return null; } @Override public Void visitArrayInitializer(ArrayInitializerContext ctx) { write("{"); if(ctx.variableInitializerList() != null) visitVariableInitializerList(ctx.variableInitializerList()); write("}"); return null; } @Override public Void visitVariableInitializerList(VariableInitializerListContext ctx) { for (int i = 0; i < ctx.getChildCount(); i++) { if(ctx.getChild(i) instanceof VariableInitializerContext) visitVariableInitializer((VariableInitializerContext) ctx.getChild(i)); else write(", "); } return null; } @Override public Void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatementContext ctx) { for (VariableDeclaratorContext var : ctx.localVariableDeclaration().variableDeclaratorList().variableDeclarator()) { boolean shouldUseAssert = var.variableInitializer() != null && isCastOutsideOfInstanceof(ctx.localVariableDeclaration(), var); Node n = scopeTree.getNode(var.variableDeclaratorId(), scopeTree.root); if (!shouldUseAssert && useValues && var.variableInitializer() != null) { write("value"); } else { if (shouldUseAssert) { write("assert(is "); } else if (n.variable && !hasModifier(ctx.localVariableDeclaration().variableModifier(), "final")) { write("variable "); } // TODO int a[] should be converted to IntArray, but unfortunately at this point we can't know that it's an array // we should do some sort of lookahead :( visitUnannType(ctx.localVariableDeclaration().unannType()); if(n.optional) write("?"); } write(" "); String identifier = var.variableDeclaratorId().Identifier().getText(); write(escapeIdentifier(identifier, true)); if (var.variableInitializer() != null) { write(" = "); visitVariableInitializer(var.variableInitializer()); } if (shouldUseAssert) { write(")"); } write(";\n"); } return null; } @Override public Void visitLocalVariableDeclaration(LocalVariableDeclarationContext ctx) { for (VariableDeclaratorContext var : ctx.variableDeclaratorList().variableDeclarator()) { VariableDeclaratorIdContext context = var.variableDeclaratorId(); Node n = scopeTree.getNode(context, scopeTree.root); if (useValues && var.variableInitializer() != null) { write("value"); } else { if (n.variable && !hasModifier(ctx.variableModifier(), "final")) { write("variable "); } visitUnannType(ctx.unannType()); if(n.optional) write("?"); } write(" "); write(escapeIdentifier(context.Identifier().getText(), true)); if (var.variableInitializer() != null) { write(" = "); visitVariableInitializer(var.variableInitializer()); } write(";\n"); } return null; } @Override public Void visitClassInstanceCreationExpression_lfno_primary(ClassInstanceCreationExpression_lfno_primaryContext ctx) { boolean isObjectSatisfying = false; if (ctx.classBody() != null) { if (ctx.argumentList() == null) { isObjectSatisfying = true; write("object satisfies "); } else { write("object extends "); } } for (int i = 0; i < ctx.Identifier().size(); i++) { if (i > 0) { write("."); } write(ctx.Identifier().get(i).getText()); } // TODO other identifiers if (ctx.typeArgumentsOrDiamond() != null) { visitTypeArgumentsOrDiamond(ctx.typeArgumentsOrDiamond()); } if (!isObjectSatisfying) { write("("); } if (ctx.argumentList() != null) { visitArgumentList(ctx.argumentList()); } if (!isObjectSatisfying) { write(")"); } if (ctx.classBody() != null) { visitClassBody(ctx.classBody()); } return null; } @Override public Void visitConditionalExpression(ConditionalExpressionContext ctx) { if (isTernaryOperator(ctx)) { // ternary operator write("if ("); visitConditionalOrExpression(ctx.conditionalOrExpression()); write(") then "); visitExpression(ctx.expression()); write(" else "); visitConditionalExpression(ctx.conditionalExpression()); } else { super.visitConditionalExpression(ctx); } return null; } @Override public Void visitExpressionName(ExpressionNameContext ctx) { if (ctx.ambiguousName() != null) { visitAmbiguousName(ctx.ambiguousName()); write("."); } boolean shouldBeLc = ctx.getParent() instanceof ArrayAccessContext || ctx.getParent() instanceof ArrayAccess_lfno_primaryContext || ctx.getParent() instanceof PostfixExpressionContext; write(escapeIdentifier(ctx.Identifier().getText(), shouldBeLc)); return null; } @Override public Void visitAmbiguousName(AmbiguousNameContext ctx) { if (ctx.ambiguousName() != null) { visitAmbiguousName(ctx.ambiguousName()); write("."); } write(ctx.Identifier().getText()); return null; } @Override public Void visitArgumentList(ArgumentListContext ctx) { for (int i = 0; i < ctx.getChildCount(); i++) { if (!(ctx.getChild(i) instanceof ExpressionContext)) { continue; } if (i > 0) { write(", "); } visitExpression((ExpressionContext) ctx.getChild(i)); } return null; } @Override public Void visitIfThenStatement(IfThenStatementContext ctx) { write("if ("); visitExpression(ctx.expression()); write(") "); if (!isBlock(ctx.statement())) { write("{\n"); } visitStatement(ctx.statement()); if (!isBlock(ctx.statement())) { write("}\n"); } return null; } @Override public Void visitIfThenElseStatement(IfThenElseStatementContext ctx) { write("if ("); visitExpression(ctx.expression()); write(") "); if (!isBlock(ctx.statementNoShortIf())) { write("{\n"); } visitStatementNoShortIf(ctx.statementNoShortIf()); if (!isBlock(ctx.statementNoShortIf())) { write("}\n"); } write("else "); if (!isBlock(ctx.statement()) && !isIf(ctx.statement())) { write("{\n"); } visitStatement(ctx.statement()); if (!isBlock(ctx.statement()) && !isIf(ctx.statement())) { write("}\n"); } return null; } @Override public Void visitBasicForStatement(BasicForStatementContext ctx) { // TODO can we detect for(i = 0; i < n; i++) and transform it to for (i in 0..n) ?? if (ctx.forInit() != null) { visitForInit(ctx.forInit()); } write("while("); if (ctx.expression() != null) { visitExpression(ctx.expression()); } else { write("true"); } write(") "); if (!isBlock(ctx.statement())) { write("{\n"); } visitStatement(ctx.statement()); if (ctx.forUpdate() != null) { visitForUpdate(ctx.forUpdate()); } write("}\n"); return null; } @Override public Void visitEnhancedForStatement(EnhancedForStatementContext ctx) { write("for ("); if (!useValues) { visitUnannType(ctx.unannType()); write(" "); } write(escapeIdentifier(ctx.variableDeclaratorId().getText(), true)); write(" in "); visitExpression(ctx.expression()); write(") "); if (!isBlock(ctx.statement())) { write("{\n"); } visitStatement(ctx.statement()); if (!isBlock(ctx.statement())) { write("}\n"); } return null; } @Override public Void visitSwitchStatement(SwitchStatementContext ctx) { write("switch ("); visitExpression(ctx.expression()); write(")\n"); return visitSwitchBlock(ctx.switchBlock()); } @Override public Void visitSwitchBlock(SwitchBlockContext ctx) { boolean hasElse = false; for (SwitchBlockStatementGroupContext group : ctx.switchBlockStatementGroup()) { // TODO transform `case a: case b:` to `case (a|b)` SwitchLabelContext firstLabel = group.switchLabels().switchLabel(0); if (firstLabel.getChild(0).getText().equals("case")) { write("case ("); boolean first = true; for (SwitchLabelContext label : group.switchLabels().switchLabel()) { if (first) { first = false; } else { write(" | "); } if (label.constantExpression() != null) { visitConstantExpression(label.constantExpression()); } else { visitEnumConstantName(label.enumConstantName()); } } write(") {\n"); } else { hasElse = true; write("else {\n"); } visitBlockStatements(group.blockStatements()); write("}\n"); } if (!hasElse) { write("else {}\n"); } return null; } @Override public Void visitBlock(BlockContext ctx) { write("{\n"); super.visitBlock(ctx); if (!isBlockInDoWhile(ctx)) { write("}\n"); } return null; } @Override public Void visitDoStatement(DoStatementContext ctx) { write("while (true) "); if (!isBlock(ctx.statement())) { write("{\n"); } visitStatement(ctx.statement()); write("if ("); visitExpression(ctx.expression()); write(") {break;}\n"); write("}\n"); return null; } @Override public Void visitPrimaryNoNewArray(PrimaryNoNewArrayContext ctx) { if(ctx.getChild(2) != null && ctx.getChild(2).getText().equals("class")) write("`" + ctx.getChild(0).getText() + "`"); else switch (ctx.getChild(0).getText()) { case "this": write("this"); break; case "(": write("("); visitExpression(ctx.expression()); write(")"); break; default: super.visitPrimaryNoNewArray(ctx); } return null; } @Override public Void visitPrimaryNoNewArray_lfno_primary(PrimaryNoNewArray_lfno_primaryContext ctx) { if(ctx.getChild(2) != null && ctx.getChild(2).getText().equals("class")) write("`" + ctx.getChild(0).getText() + "`"); else switch (ctx.getChild(0).getText()) { case "this": write("this"); break; case "(": write("("); visitExpression(ctx.expression()); write(")"); break; default: super.visitPrimaryNoNewArray_lfno_primary(ctx); } return null; } @Override public Void visitConditionalOrExpression(ConditionalOrExpressionContext ctx) { if (ctx.conditionalOrExpression() != null) { visitConditionalOrExpression(ctx.conditionalOrExpression()); write(" || "); } return visitConditionalAndExpression(ctx.conditionalAndExpression()); } @Override public Void visitConditionalAndExpression(ConditionalAndExpressionContext ctx) { if (ctx.conditionalAndExpression() != null) { visitConditionalAndExpression(ctx.conditionalAndExpression()); if (isInIfCondition(ctx)) { write(", "); } else { write(" && "); } } return visitInclusiveOrExpression(ctx.inclusiveOrExpression()); } @Override public Void visitInclusiveOrExpression(InclusiveOrExpressionContext ctx) { if (ctx.inclusiveOrExpression() != null) { visitInclusiveOrExpression(ctx.inclusiveOrExpression()); write(" | "); } return visitExclusiveOrExpression(ctx.exclusiveOrExpression()); } @Override public Void visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) { if (ctx.exclusiveOrExpression() != null) { visitExclusiveOrExpression(ctx.exclusiveOrExpression()); write(" ^ "); } return visitAndExpression(ctx.andExpression()); } @Override public Void visitAndExpression(AndExpressionContext ctx) { if (ctx.andExpression() != null) { visitAndExpression(ctx.andExpression()); write(" & "); } return visitEqualityExpression(ctx.equalityExpression()); } @Override public Void visitEqualityExpression(EqualityExpressionContext ctx) { if (ctx.equalityExpression() != null) { String operator = ctx.getChild(1).getText(); if (ctx.relationalExpression().getText().equals("null")) { if (operator.equals("==")) { write("!"); } if (ctx.equalityExpression().getText().matches("\\w+") && isInIfCondition(ctx)) { write("exists "); visitEqualityExpression(ctx.equalityExpression()); } else { visitEqualityExpression(ctx.equalityExpression()); write(" exists"); } return null; } else { visitEqualityExpression(ctx.equalityExpression()); write(" " + operator + " "); } } return visitRelationalExpression(ctx.relationalExpression()); } @Override public Void visitRelationalExpression(RelationalExpressionContext ctx) { if (ctx.relationalExpression() != null) { String operator = ctx.getChild(1).getText(); if (operator.equals("instanceof")) { if (ctx.relationalExpression().getText().matches("\\w+") && isInIfCondition(ctx) && !isExpression(ctx)) { write("is "); visitReferenceType(ctx.referenceType()); write(" "); visitRelationalExpression(ctx.relationalExpression()); } else { visitRelationalExpression(ctx.relationalExpression()); write(" is "); visitReferenceType(ctx.referenceType()); } return null; } visitRelationalExpression(ctx.relationalExpression()); write(" "); write(operator.replace("instanceof", "is")); write(" "); } if (ctx.shiftExpression() != null) { visitShiftExpression(ctx.shiftExpression()); } else { visitReferenceType(ctx.referenceType()); } return null; } @Override public Void visitShiftExpression(ShiftExpressionContext ctx) { if (ctx.shiftExpression() != null) { visitShiftExpression(ctx.shiftExpression()); write(" << "); } return visitAdditiveExpression(ctx.additiveExpression()); } @Override public Void visitAdditiveExpression(AdditiveExpressionContext ctx) { if (ctx.additiveExpression() != null) { visitAdditiveExpression(ctx.additiveExpression()); write(" " + ctx.getChild(1).getText() + " "); } return visitMultiplicativeExpression(ctx.multiplicativeExpression()); } @Override public Void visitMultiplicativeExpression(MultiplicativeExpressionContext ctx) { if (ctx.multiplicativeExpression() != null) { visitMultiplicativeExpression(ctx.multiplicativeExpression()); write(" " + ctx.getChild(1).getText() + " "); } return visitUnaryExpression(ctx.unaryExpression()); } @Override public Void visitUnaryExpression(UnaryExpressionContext ctx) { String op = ctx.getChild(0).getText(); if (op.equals("+") || op.equals("-")) { write(op); return visitUnaryExpression(ctx.unaryExpression()); } else { return super.visitUnaryExpression(ctx); } } @Override public Void visitPreIncrementExpression(PreIncrementExpressionContext ctx) { write("++"); return super.visitPreIncrementExpression(ctx); } @Override public Void visitPreDecrementExpression(PreDecrementExpressionContext ctx) { write("--"); return super.visitPreDecrementExpression(ctx); } @Override public Void visitPostIncrementExpression(PostIncrementExpressionContext ctx) { super.visitPostIncrementExpression(ctx); write("++"); return null; } @Override public Void visitPostIncrementExpression_lf_postfixExpression( PostIncrementExpression_lf_postfixExpressionContext ctx) { super.visitPostIncrementExpression_lf_postfixExpression(ctx); write("++"); return null; } @Override public Void visitPostDecrementExpression( PostDecrementExpressionContext ctx) { super.visitPostDecrementExpression(ctx); write("--"); return null; } @Override public Void visitPostDecrementExpression_lf_postfixExpression( PostDecrementExpression_lf_postfixExpressionContext ctx) { super.visitPostDecrementExpression_lf_postfixExpression(ctx); write("--"); return null; } @Override public Void visitUnaryExpressionNotPlusMinus( UnaryExpressionNotPlusMinusContext ctx) { if (ctx.getChild(0).getText().equals("!")) { write("!"); } return super.visitUnaryExpressionNotPlusMinus(ctx); } @Override public Void visitCastExpression(CastExpressionContext ctx) { if (ctx.unaryExpression() != null) { visitUnaryExpression(ctx.unaryExpression()); } if (ctx.unaryExpressionNotPlusMinus() != null) { visitUnaryExpressionNotPlusMinus(ctx.unaryExpressionNotPlusMinus()); } if (ctx.lambdaExpression() != null) { visitLambdaExpression(ctx.lambdaExpression()); } return null; } @Override public Void visitLambdaExpression(LambdaExpressionContext ctx) { visitLambdaParameters(ctx.lambdaParameters()); if (ctx.lambdaBody().block() == null) { write(" => "); } visitLambdaBody(ctx.lambdaBody()); return null; } @Override public Void visitLambdaParameters(LambdaParametersContext ctx) { write("("); if (ctx.Identifier() != null) { write(ctx.Identifier().getText()); } super.visitLambdaParameters(ctx); write(")"); return null; } @Override public Void visitInferredFormalParameterList(InferredFormalParameterListContext ctx) { boolean isFirst = true; for (TerminalNode i : ctx.Identifier()) { if (!isFirst) { write(", "); } write(i.getText()); isFirst = false; } return null; } @Override public Void visitEnumDeclaration(EnumDeclarationContext ctx) { if (hasModifier(ctx.classModifier(), "public")) { write("shared "); } if (hasModifier(ctx.classModifier(), "static")) { write("static "); } write("class "); write(ctx.Identifier().getText()); if (ctx.superinterfaces() != null) { visitSuperinterfaces(ctx.superinterfaces()); } return visitEnumBody(ctx.enumBody()); } @Override public Void visitEnumBody(EnumBodyContext ctx) { write(" {\n"); write("shared actual String string;\n"); if (ctx.enumBodyDeclarations() != null) { for (ClassBodyDeclarationContext classBody : ctx .enumBodyDeclarations().classBodyDeclaration()) { if (classBody.constructorDeclaration() != null) { // Special case, we need to add an extra "String string" // parameter visitEnumConstructorDeclaration( classBody.constructorDeclaration()); } else { visitClassBodyDeclaration(classBody); } } } if (ctx.enumConstantList() != null) { visitEnumConstantList(ctx.enumConstantList()); } write("}\n"); return null; } private void visitEnumConstructorDeclaration( ConstructorDeclarationContext ctx) { write("abstract new \\i"); write(ctx.constructorDeclarator().simpleTypeName().getText()); write("(String string, "); if (ctx.constructorDeclarator().formalParameterList() != null) { visitFormalParameterList( ctx.constructorDeclarator().formalParameterList()); } write(")"); write(" {\n"); write("this.string = string;\n"); super.visitConstructorBody(ctx.constructorBody()); write("}\n\n"); } @Override public Void visitEnumConstant(EnumConstantContext ctx) { write("shared new \\i"); write(ctx.Identifier().getText()); if (ctx.argumentList() == null) { write(" { string = \""); write(ctx.Identifier().getText()); write("\"; }\n"); } else { write(" extends \\i"); write(((EnumDeclarationContext) ctx.getParent().getParent() .getParent()).Identifier().getText()); write("(\""); write(ctx.Identifier().getText()); write("\", "); visitArgumentList(ctx.argumentList()); write(") { }\n"); } return null; } @Override public Void visitFieldDeclaration(FieldDeclarationContext ctx) { for (VariableDeclaratorContext var : ctx.variableDeclaratorList() .variableDeclarator()) { VariableDeclaratorIdContext context = var.variableDeclaratorId(); Node n = scopeTree.getNode(context, scopeTree.root); if (hasModifier(ctx.fieldModifier(), "public")) { write("shared "); } if (hasModifier(ctx.fieldModifier(), "static")) { write("static "); } if (useValues && var.variableInitializer() != null && !hasModifier(ctx.fieldModifier(), "public") && !hasModifier(ctx.fieldModifier(), "protected")) { write("value"); } else { if (n.variable && !hasModifier(ctx.fieldModifier(), "final")) { write("variable "); } visitUnannType(ctx.unannType()); if(n.optional) write("?"); } write(" "); write(escapeIdentifier(var.variableDeclaratorId().getText(), true)); if (var.variableInitializer() != null) { write(" = "); visitVariableInitializer(var.variableInitializer()); } write(";\n"); } return null; } @Override public Void visitFieldAccess(FieldAccessContext ctx) { visitPrimary(ctx.primary()); write("."); write(escapeIdentifier(ctx.Identifier().getText(), true)); return null; } @Override public Void visitFieldAccess_lf_primary(FieldAccess_lf_primaryContext ctx) { write("."); write(escapeIdentifier(ctx.Identifier().getText(), true)); return null; } @Override public Void visitTryStatement(TryStatementContext ctx) { if (ctx.tryWithResourcesStatement() == null) { write("try "); } return super.visitTryStatement(ctx); } @Override public Void visitCatchClause(CatchClauseContext ctx) { write("catch ("); visitCatchFormalParameter(ctx.catchFormalParameter()); write(") "); return visitBlock(ctx.block()); } @Override public Void visitTryWithResourcesStatement( TryWithResourcesStatementContext ctx) { write("try "); return super.visitTryWithResourcesStatement(ctx); } @Override public Void visitResourceSpecification(ResourceSpecificationContext ctx) { write("("); visitResourceList(ctx.resourceList()); write(") "); return null; } @Override public Void visitUnannClassType(UnannClassTypeContext ctx) { if (ctx.unannClassOrInterfaceType() != null) { visitUnannClassOrInterfaceType(ctx.unannClassOrInterfaceType()); write("."); write(ctx.Identifier().getText()); } else { write(ctx.Identifier().getText()); } if (ctx.typeArguments() != null) { visitTypeArguments(ctx.typeArguments()); } return null; } @Override public Void visitResourceList(ResourceListContext ctx) { int i = 0; for (ResourceContext resource : ctx.resource()) { if (i > 0) { write("; "); } visitResource(resource); i++; } return null; } @Override public Void visitResource(ResourceContext ctx) { visitUnannType(ctx.unannType()); write(" "); visitVariableDeclaratorId(ctx.variableDeclaratorId()); write(" = "); return visitExpression(ctx.expression()); } @Override public Void visitCatchType(CatchTypeContext ctx) { visitUnannClassType(ctx.unannClassType()); for (ClassTypeContext ct : ctx.classType()) { write(" | "); visitClassType(ct); } write(" "); return null; } @Override public Void visitVariableDeclaratorId(VariableDeclaratorIdContext ctx) { write(escapeIdentifier(ctx.Identifier().getText(), true)); return null; } @Override public Void visitFinally_(Finally_Context ctx) { write("finally "); return super.visitFinally_(ctx); } @Override public Void visitWhileStatement(WhileStatementContext ctx) { write("while ("); visitExpression(ctx.expression()); write(") "); if (ctx.statement().statementWithoutTrailingSubstatement() != null && ctx.statement().statementWithoutTrailingSubstatement() .block() != null) { visitStatement(ctx.statement()); } else { write("{\n"); visitStatement(ctx.statement()); write("}\n"); } return null; } @Override public Void visitWhileStatementNoShortIf( WhileStatementNoShortIfContext ctx) { write("while ("); visitExpression(ctx.expression()); write(") "); if (ctx.statementNoShortIf() .statementWithoutTrailingSubstatement() != null && ctx.statementNoShortIf() .statementWithoutTrailingSubstatement() .block() != null) { visitStatementNoShortIf(ctx.statementNoShortIf()); } else { write("{\n"); visitStatementNoShortIf(ctx.statementNoShortIf()); write("}\n"); } return null; } @Override public Void visitBreakStatement(BreakStatementContext ctx) { if (parent(ctx, 6) instanceof SwitchBlockContext) { return null; } write("break;\n"); return null; } @Override public Void visitContinueStatement(ContinueStatementContext ctx) { write("continue;\n"); return null; } @Override public Void visitStatementExpressionList( StatementExpressionListContext ctx) { super.visitStatementExpressionList(ctx); write(";\n"); return null; } @Override public Void visitAssertStatement(AssertStatementContext ctx) { if (ctx.expression().size() > 1) { if (!ctx.expression(1).getText().matches("\"[^\"]*\"")) { write("// "); } visitExpression(ctx.expression(1)); write("\n"); } write("assert("); visitExpression(ctx.expression(0)); write(");\n"); return null; } private boolean isBlock(StatementContext ctx) { return ctx.statementWithoutTrailingSubstatement() != null && ctx.statementWithoutTrailingSubstatement().block() != null; } private boolean isIf(StatementContext ctx) { return ctx.ifThenStatement() != null || ctx.ifThenElseStatement() != null; } private boolean isBlock(StatementNoShortIfContext ctx) { return ctx.statementWithoutTrailingSubstatement() != null && ctx.statementWithoutTrailingSubstatement().block() != null; } private boolean isBlockInDoWhile(BlockContext block) { return block .getParent() instanceof StatementWithoutTrailingSubstatementContext && block.getParent().getParent() instanceof StatementContext && (block.getParent().getParent() .getParent() instanceof DoStatementContext || block.getParent().getParent() .getParent() instanceof BasicForStatementContext); } private boolean isExpression(RelationalExpressionContext ctx) { for (int i = 1; i <= 8; i++) { if (parent(ctx, i).getChildCount() > 1) { return true; } } return false; } private boolean isInIfCondition(ConditionalAndExpressionContext ctx) { while (ctx.getParent() instanceof ConditionalAndExpressionContext) { ctx = (ConditionalAndExpressionContext) ctx.getParent(); } return parent(ctx, 1) instanceof ConditionalOrExpressionContext && parent(ctx, 2) instanceof ConditionalExpressionContext && isInIfCondition( (ConditionalExpressionContext) parent(ctx, 2)); } private boolean isInIfCondition(ConditionalExpressionContext ctx) { if (parent(ctx, 1) instanceof AssignmentExpressionContext && parent(ctx, 2) instanceof ExpressionContext) { ParserRuleContext candidate = parent(ctx, 3); return candidate instanceof IfThenElseStatementContext || candidate instanceof IfThenStatementContext || candidate instanceof IfThenElseStatementNoShortIfContext; } return false; } private boolean isInIfCondition(RelationalExpressionContext ctx) { if (ctx.getParent() instanceof EqualityExpressionContext && parent(ctx, 2) instanceof AndExpressionContext && parent(ctx, 3) instanceof ExclusiveOrExpressionContext && parent(ctx, 4) instanceof InclusiveOrExpressionContext && parent(ctx, 5) instanceof ConditionalAndExpressionContext && parent(ctx, 6) instanceof ConditionalOrExpressionContext && parent(ctx, 7) instanceof ConditionalExpressionContext) { ConditionalExpressionContext condExpr = (ConditionalExpressionContext) parent( ctx, 7); if (isTernaryOperator(condExpr)) { return true; } else { return isInIfCondition(condExpr); } } return false; } private boolean isInIfCondition(EqualityExpressionContext ctx) { if (ctx.getParent() instanceof AndExpressionContext && parent(ctx, 2) instanceof ExclusiveOrExpressionContext && parent(ctx, 3) instanceof InclusiveOrExpressionContext && parent(ctx, 4) instanceof ConditionalAndExpressionContext && parent(ctx, 5) instanceof ConditionalOrExpressionContext && parent(ctx, 6) instanceof ConditionalExpressionContext) { ConditionalExpressionContext condExpr = (ConditionalExpressionContext) parent( ctx, 6); if (isTernaryOperator(condExpr)) { return true; } else { return isInIfCondition(condExpr); } } return false; } private boolean isTernaryOperator(ConditionalExpressionContext ctx) { return ctx.getChildCount() > 1; } private ParserRuleContext parent(ParserRuleContext ctx, int level) { for (int i = 0; i < level; i++) { if (ctx != null) { ctx = ctx.getParent(); } } return ctx; } private boolean isCastOutsideOfInstanceof( LocalVariableDeclarationContext ctx, VariableDeclaratorContext var) { // checks if this involves a cast if (getInnerChild(var.variableInitializer(), ExpressionContext.class, AssignmentExpressionContext.class, ConditionalExpressionContext.class, ConditionalOrExpressionContext.class, ConditionalAndExpressionContext.class, InclusiveOrExpressionContext.class, ExclusiveOrExpressionContext.class, AndExpressionContext.class, EqualityExpressionContext.class, RelationalExpressionContext.class, ShiftExpressionContext.class, AdditiveExpressionContext.class, MultiplicativeExpressionContext.class, UnaryExpressionContext.class, UnaryExpressionNotPlusMinusContext.class, CastExpressionContext.class) == null) { return false; } // checks if the variable declaration is located inside an if if (hasParents(ctx, LocalVariableDeclarationStatementContext.class, BlockStatementContext.class, BlockStatementsContext.class, BlockContext.class, StatementWithoutTrailingSubstatementContext.class)) { StatementWithoutTrailingSubstatementContext st = (StatementWithoutTrailingSubstatementContext) parent( ctx, 5); if (hasParents(st, StatementNoShortIfContext.class, IfThenElseStatementContext.class)) { // checks if the condition involves an instanceof return !isInstanceofCondition( ((IfThenElseStatementContext) st.getParent() .getParent()).expression(), var.variableDeclaratorId().getText()); } else if (hasParents(st, StatementContext.class, IfThenStatementContext.class)) { // checks if the condition involves an instanceof return !isInstanceofCondition( ((IfThenStatementContext) st.getParent().getParent()) .expression(), ""/* TODO extract casted identifier */); } } return true; } private boolean isInstanceofCondition(ExpressionContext expr, String identifier) { RelationalExpressionContext child = (RelationalExpressionContext) getInnerChild( expr, AssignmentExpressionContext.class, ConditionalExpressionContext.class, ConditionalOrExpressionContext.class, ConditionalAndExpressionContext.class, InclusiveOrExpressionContext.class, ExclusiveOrExpressionContext.class, AndExpressionContext.class, EqualityExpressionContext.class, RelationalExpressionContext.class); if (child == null) { return false; } if (child.getChildCount() > 1 && child.getChild(1).getText().equals("instanceof")) { return true; // TODO compare identifiers } return false; } private boolean hasParents(ParserRuleContext ctx, Class<?>... parents) { if (parents != null) { ParserRuleContext parent = ctx; for (Class<?> p : parents) { parent = parent.getParent(); if (!p.isAssignableFrom(parent.getClass())) { return false; } } } return true; } private ParseTree getInnerChild(ParserRuleContext ctx, Class<?>... children) { if (children != null) { ParseTree rule = ctx; for (Class<?> p : children) { boolean foundChild = false; for (int i = 0; i < rule.getChildCount(); i++) { ParseTree child = rule.getChild(i); if (p.isAssignableFrom(child.getClass())) { rule = child; foundChild = true; break; } } if (!foundChild) { return null; } } return rule; } return null; } private String escapeIdentifier(String identifier, boolean shouldBeLowercase) { if (RESERVED_KEYWORDS.contains(identifier)) { return "\\i" + identifier; } else if(is_CONSTANT_CASE(identifier.toCharArray())) { return constant_case_toCamelCase(identifier.toCharArray()); } else if (shouldBeLowercase && identifier.charAt(0) != '_' && !Character.isLowerCase(identifier.charAt(0))) { return "\\i" + identifier; } return identifier; } private static String constant_case_toCamelCase(char[] newName) { int j = 0; boolean capitaliseNext = false; for(int i=0;i<newName.length;i++){ char codepoint = newName[i]; if(codepoint == '_'){ // skip underscore capitaliseNext = true; }else if(capitaliseNext){ newName[j++] = codepoint; capitaliseNext = false; }else{ newName[j++] = Character.toLowerCase(codepoint); } } return new String(newName, 0, j); } private static boolean is_CONSTANT_CASE(char[] newName) { // reject empty, "U" and "_" if(newName.length <= 1) return false; for(int i=0;i<newName.length;i++){ int codepoint = newName[i]; if(Character.isLowerCase(codepoint) && codepoint != '_') return false; } return true; } }